/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { parseArkStmt } from '../../Ark2Graph/nodeUtils/stmts';
import {
    NumberType,
    UnknownType,
    Local,
    ArkNewArrayExpr,
    ArkAwaitExpr,
    ArkYieldExpr,
    AnyType,
    BooleanType,
    StringType,
    NullType,
    UndefinedType,
    LiteralType,
    VoidType,
    NeverType,
    UnclearReferenceType,
    Type,
    UnionType,
    IntersectionType,
    MethodSignature,
    FunctionType,
    ClassSignature,
    FileSignature,
    NamespaceSignature,
    MethodSubSignature,
    LexicalEnvType,
    AnnotationTypeQueryType,
    AnnotationNamespaceType,
    ClassType,
    ArrayType,
    TupleType,
    GenericType,
    AliasType,
    AliasTypeSignature,
    AbstractInvokeExpr,
    Value,
    ArkConditionExpr,
    RelationalBinaryOperator,
    AbstractRef,
    AbstractExpr,
    ArkArrayRef,
    AbstractFieldRef,
} from '../../ArkAnalyzer/src/';
import {
    ArkAssignStmt,
    ArkIfStmt,
    ArkInvokeStmt,
    ArkReturnStmt,
    ArkReturnVoidStmt,
    ArkThrowStmt,
} from '../../ArkAnalyzer/src/core/base/Stmt';
import { ArkParameterRef } from '../../ArkAnalyzer/src/';
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
import { Constant, NumberConstant, StringConstant } from '../../ArkAnalyzer/src/core/base/Constant';
import { parseArkType } from '../../Ark2Graph/nodeUtils/types';
import { parseArkValue } from '../../Ark2Graph/nodeUtils/values';
/*
    测试项：Ark2Graph的节点构建功能
    标题：判断AST属性转化后是否符合json格式
    级别：关键
    预置条件：无
    操作步骤：1.使用mockType模拟输入的属性
             2.对该数据的文件运行Node暴露的属性转化接口
    预期结果：1.结果是否和规定的json格式一致
    测试结果：符合预期结果
*/
class MockType extends Type {
    getTypeString(): string {
        return 'mock';
    }
    private typeName: string;

    constructor(typeName: string) {
        super();
        this.typeName = typeName;
    }

    toString(): string {
        return this.typeName;
    }
}

const mockFileSignature = new FileSignature('mockProject', 'mockFile');
const mockNamespaceSignature = new NamespaceSignature('mockNamespace', mockFileSignature);

const mockClassSignature = {
    getDeclaringFileSignature: jest.fn().mockReturnValue(mockFileSignature),
    getDeclaringNamespaceSignature: jest.fn().mockReturnValue(mockNamespaceSignature),
    getClassName: jest.fn().mockReturnValue('MyClass'),
    toString: jest.fn().mockReturnValue('Class(MyClass)'),
    toMapKey: jest.fn().mockReturnValue('Key(MyClass)'),
} as unknown as ClassSignature;

const mockReturnType = new MockType('mockReturnType');

const mockMethodSubSignature = {
    getReturnType: jest.fn().mockReturnValue(mockReturnType),
    getMethodName: jest.fn().mockReturnValue('mockMethod'),
    getParameters: jest.fn().mockReturnValue([]),
    getParameterTypes: jest.fn().mockReturnValue([]),
    setReturnType: jest.fn(),
    isStatic: jest.fn().mockReturnValue(false),
    toString: jest.fn().mockReturnValue('mockMethod()'),
} as unknown as MethodSubSignature;

const mockMethodSignature = new MethodSignature(mockClassSignature, mockMethodSubSignature);

class MockInvokeExpr extends AbstractInvokeExpr {
    constructor(methodSignature: MethodSignature, args: Value[], realGenericTypes?: Type[]) {
        super(methodSignature, args, realGenericTypes);
    }
    public toString(): string {
        return `MockInvokeExpr(${this.getMethodSignature().toString()})`;
    }
}
describe('Testing Type Conversions', () => {
    test('getType should return correct JSON for NumberType', () => {
        const mockType = NumberType.getInstance();
        jest.spyOn(mockType, 'getName').mockReturnValue('int');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'NumberType',
            name: 'int',
        });
    });

    test('getType should return correct JSON for UnknownType', () => {
        const mockType = UnknownType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('unknown');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'UnknownType',
            name: 'unknown',
        });
    });

    test('getType should return correct JSON for AnyType', () => {
        const mockType = AnyType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('any');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'AnyType',
            name: 'any',
        });
    });

    test('getType should return correct JSON for BooleanType', () => {
        const mockType = BooleanType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('boolean');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'BooleanType',
            name: 'boolean',
        });
    });

    test('getType should return correct JSON for StringType', () => {
        const mockType = StringType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('string');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'StringType',
            name: 'string',
        });
    });

    test('getType should return correct JSON for NullType', () => {
        const mockType = NullType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('null');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'NullType',
            name: 'null',
        });
    });

    test('getType should return correct JSON for UndefinedType', () => {
        const mockType = UndefinedType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('undefined');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'UndefinedType',
            name: 'undefined',
        });
    });

    test('getType should return correct JSON for LiteralType', () => {
        const mockType = new LiteralType('undefined');
        jest.spyOn(mockType, 'getTypeString').mockReturnValue('undefined');

        const result = parseArkType(mockType);

        expect(result).toEqual({
            _identifier: 'LiteralType',
            literalName: 'undefined',
        });
    });

    test('getType should return correct JSON for VoidType', () => {
        const mockType = VoidType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('void');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'VoidType',
        });
    });

    test('getType should return correct JSON for NeverType', () => {
        const mockType = NeverType.getInstance();
        jest.spyOn(mockType, 'toString').mockReturnValue('never');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'NeverType',
        });
    });

    test('getType should return correct JSON for UnclearReferenceType', () => {
        const mockType = new UnclearReferenceType('unclear');
        jest.spyOn(mockType, 'toString').mockReturnValue('unclearreference');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'UnclearReferenceType',
            name: 'unclear',
        });
    });

    test('getType should return correct JSON for UnionType', () => {
        const mockGenericType = new MockType('ark');
        const mockType = new UnionType([mockGenericType]);
        jest.spyOn(mockType, 'toString').mockReturnValue('union');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'UnionType',
            currType: {
                _identifier: "UnknownType",
                name: "unknown"
            },
            types: [
                {
                    _identifier: 'MockType',
                },
            ],
        });
    });

    test('getType should return correct JSON for IntersectionType', () => {
        const mockGenericType = new MockType('ark');
        const mockType = new IntersectionType([mockGenericType]);
        jest.spyOn(mockType, 'toString').mockReturnValue('intersection');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'IntersectionType',
        });
    });

    test('getType should return correct JSON for FunctionType', () => {
        const mockType = new FunctionType(mockMethodSignature);
        jest.spyOn(mockType, 'toString').mockReturnValue('function');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'FunctionType',
            name: 'Class(MyClass).mockMethod()',
        });
    });

    test('getType should return correct JSON for LexicalEnvType', () => {
        const mockType = new LexicalEnvType(mockMethodSignature);
        jest.spyOn(mockType, 'toString').mockReturnValue('lexical');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'LexicalEnvType',
        });
    });

    test('getType should return correct JSON for AnnotationTypeQueryType', () => {
        const mockType = new AnnotationTypeQueryType('test');
        jest.spyOn(mockType, 'toString').mockReturnValue('annottion');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'AnnotationTypeQueryType',
            originType: 'test',
        });
    });

    test('getType should return correct JSON for AnnotationNamespaceType', () => {
        const mockType = new AnnotationNamespaceType('test');
        jest.spyOn(mockType, 'toString').mockReturnValue('annottion');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'AnnotationNamespaceType',
            originType: 'test',
        });
    });

    test('getType should return correct JSON for ClassType', () => {
        const mockType = new ClassType(mockClassSignature);
        jest.spyOn(mockType, 'toString').mockReturnValue('class');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'ClassType',
            name: 'Class(MyClass)',
        });
    });

    test('getType should return correct JSON for ArrayType', () => {
        const tempMockType = new MockType('test');
        const mockType = new ArrayType(tempMockType, 3);
        jest.spyOn(mockType, 'toString').mockReturnValue('array');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'ArrayType',
            baseType: {
                _identifier: 'MockType',
            },
            dimension: 3,
        });
    });

    test('getType should return correct JSON for TupleType', () => {
        const tempMockType = new MockType('test');
        const mockType = new TupleType([tempMockType]);
        jest.spyOn(mockType, 'toString').mockReturnValue('tuple');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'TupleType',
            types: [
                {
                    _identifier: 'MockType',
                },
            ],
        });
    });

    test('getType should return correct JSON for GenericType', () => {
        const mockType = new GenericType('test');
        jest.spyOn(mockType, 'toString').mockReturnValue('generic');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'GenericType',
            name: 'test',
        });
    });

    test('getType should return correct JSON for AliasType', () => {
        const tempMockType = new MockType('test');
        const mockAliasTypeSignature = new AliasTypeSignature('testAlias', mockMethodSignature);
        const mockType = new AliasType('test', tempMockType, mockAliasTypeSignature);
        jest.spyOn(mockType, 'toString').mockReturnValue('alias');

        const result = parseArkType(mockType);
        expect(result).toEqual({
            _identifier: 'AliasType',
            name: 'test',
            originalType: {
                _identifier: 'MockType',
            },
        });
    });
});

describe('Testing Value Conversions', () => {
    test('getValue should return correct JSON for Local', () => {
        const mockLocal = new Local('testVar', NumberType.getInstance());
        jest.spyOn(mockLocal, 'getName').mockReturnValue('testVar');
        jest.spyOn(mockLocal, 'getType').mockReturnValue(NumberType.getInstance());

        const result = parseArkValue(mockLocal);
        expect(result).toMatchObject({
            _identifier: 'Local',
            name: 'testVar',
            type: expect.any(Object),
        });
    });

    test('getValue should return correct JSON for AbstractRef', () => {
        const mockAbstractRef = {
            constructor: { name: 'AbstractRef' },
            getType: jest.fn().mockReturnValue(NumberType.getInstance()),
        } as unknown as AbstractRef;
        jest.spyOn(mockAbstractRef, 'getType').mockReturnValue(NumberType.getInstance());

        const result = parseArkValue(mockAbstractRef);
        expect(result).toMatchObject({
            _identifier: 'AbstractRef',
            type: expect.any(Object),
        });
    });

    test('getValue should return correct JSON for Constant', () => {
        const mockConstant = {
            constructor: { name: 'Constant' },
            getType: jest.fn().mockReturnValue(NumberType.getInstance()),
        } as unknown as Constant;
        jest.spyOn(mockConstant, 'getType').mockReturnValue(NumberType.getInstance());

        const result = parseArkValue(mockConstant);
        expect(result).toMatchObject({
            _identifier: 'Constant',
            type: expect.any(Object),
        });
    });

    test('getValue should return correct JSON for AbstractExpr', () => {
        const mockAbstractExpr = {
            constructor: { name: 'AbstractExpr' },
            getType: jest.fn().mockReturnValue(NumberType.getInstance()),
        } as unknown as AbstractExpr;

        jest.spyOn(mockAbstractExpr, 'getType').mockReturnValue(NumberType.getInstance());

        const result = parseArkValue(mockAbstractExpr);
        expect(result).toMatchObject({
            _identifier: 'AbstractExpr',
            type: expect.any(Object),
        });
    });
});

describe('Testing JSON Generation from Stmt', () => {
    test('getJson should return correct JSON for ArkAssignStmt', () => {
        const mockLeftOp = new Local('a', NumberType.getInstance());
        const mockRightOp = new Local('b', NumberType.getInstance());
        const mockStmt = new ArkAssignStmt(mockLeftOp, mockRightOp);
        jest.spyOn(mockStmt, 'getLeftOp').mockReturnValue(mockLeftOp);
        jest.spyOn(mockStmt, 'getRightOp').mockReturnValue(mockRightOp);
        jest.spyOn(mockLeftOp, 'getName').mockReturnValue('varA');
        jest.spyOn(mockRightOp, 'getName').mockReturnValue('varB');

        const result = parseArkStmt(mockStmt);
        expect(result).toMatchObject({
            _identifier: 'ArkAssignStmt',
            leftOp: { name: 'varA' },
            rightOp: { name: 'varB' },
        });
    });

    test('getJson should return correct JSON for ArkInvokeStmt', () => {
        const mockArg1 = new Local('a', NumberType.getInstance());
        const mockArg2 = new Local('b', NumberType.getInstance());
        const mockInvokeExpr = new MockInvokeExpr(mockMethodSignature, [mockArg1, mockArg2]);
        jest.spyOn(mockInvokeExpr, 'getMethodSignature').mockReturnValue(mockMethodSignature);
        jest.spyOn(mockInvokeExpr, 'getArgs').mockReturnValue([mockArg1, mockArg2]);
        const mockStmt = new ArkInvokeStmt(mockInvokeExpr);
        jest.spyOn(mockStmt, 'getInvokeExpr').mockReturnValue(mockInvokeExpr);
        const result = parseArkStmt(mockStmt);
        expect(result).toMatchObject({
            _identifier: 'ArkInvokeStmt',
            invokeExpr: { name: 'mockMethod' },
        });
    });

    test('getJson should return correct JSON for ArkIfStmt', () => {
        const mockArg1 = new Local('a', NumberType.getInstance());
        const mockArg2 = new Local('b', NumberType.getInstance());
        const mockOperator = RelationalBinaryOperator.LessThan;
        const mockConditionExpr = new ArkConditionExpr(mockArg1, mockArg2, mockOperator);
        const mockStmt = new ArkIfStmt(mockConditionExpr);
        jest.spyOn(mockStmt, 'getConditionExpr').mockReturnValue(mockConditionExpr);

        const result = parseArkStmt(mockStmt);
        expect(result).toMatchObject({
            _identifier: 'ArkIfStmt',
            conditionExpr: { _identifier: 'ArkConditionExpr' },
        });
    });

    test('getJson should return correct JSON for ArkReturnVoidStmt', () => {
        const mockStmt = new ArkReturnVoidStmt();

        const result = parseArkStmt(mockStmt);
        expect(result).toMatchObject({
            _identifier: 'ArkReturnVoidStmt',
        });
    });

    test('getJson should return correct JSON for ArkReturnStmt', () => {
        const mockOp = new Local('ret', NumberType.getInstance());
        const mockStmt = new ArkReturnStmt(mockOp);
        jest.spyOn(mockStmt, 'getOp').mockReturnValue(mockOp);
        jest.spyOn(mockOp, 'getName').mockReturnValue('retA');

        const result = parseArkStmt(mockStmt);
        expect(result).toMatchObject({
            _identifier: 'ArkReturnStmt',
            op: { name: 'retA' },
        });
    });

    test('getJson should return correct JSON for ArkThrowStmt', () => {
        const mockOp = new Local('throw', NumberType.getInstance());
        const mockStmt = new ArkThrowStmt(mockOp);
        jest.spyOn(mockStmt, 'getOp').mockReturnValue(mockOp);
        jest.spyOn(mockOp, 'getName').mockReturnValue('throwA');

        const result = parseArkStmt(mockStmt);
        expect(result).toMatchObject({
            _identifier: 'ArkThrowStmt',
            op: { name: 'throwA' },
        });
    });
});

describe('Testing Expression Value Conversions', () => {
    test('getExprValue should return correct JSON for ArkNewArrayExpr', () => {
        const mockBaseType = NumberType.getInstance();
        const mockSize = new NumberConstant(10);

        jest.spyOn(mockBaseType, 'getName').mockReturnValue('int');
        jest.spyOn(mockSize, 'getValue').mockReturnValue('10');

        const mockExpr = new ArkNewArrayExpr(mockBaseType, mockSize);
        jest.spyOn(mockExpr, 'getBaseType').mockReturnValue(mockBaseType);
        jest.spyOn(mockExpr, 'getSize').mockReturnValue(mockSize);

        let json: Record<string, any> = {};
        json = parseArkValue(mockExpr);

        expect(json).toMatchObject({
            baseType: { _identifier: 'NumberType', name: 'int' },
            size: { value: String(10) },
        });
    });

    test('getExprValue should return correct JSON for ArkAwaitExpr', () => {
        const mockPromise = new Local('promiseVar', UnknownType.getInstance());
        jest.spyOn(mockPromise, 'getName').mockReturnValue('promiseVar');

        const mockExpr = new ArkAwaitExpr(mockPromise);
        jest.spyOn(mockExpr, 'getPromise').mockReturnValue(mockPromise);

        let json: Record<string, any> = {};
        json = parseArkValue(mockExpr);

        expect(json).toMatchObject({
            promise: { name: 'promiseVar' },
        });
    });

    test('getExprValue should return correct JSON for ArkYieldExpr', () => {
        const mockYieldValue = new NumberConstant(5);
        jest.spyOn(mockYieldValue, 'getValue').mockReturnValue('5');

        const mockExpr = new ArkYieldExpr(mockYieldValue);
        jest.spyOn(mockExpr, 'getYieldValue').mockReturnValue(mockYieldValue);

        let json: Record<string, any> = {};
        json = parseArkValue(mockExpr);

        expect(json).toMatchObject({
            yieldValue: { value: String(5) },
        });
    });
});
